﻿#region

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
using AeroWizard.Design;
using AeroWizard.Properties;
using Microsoft.Win32.DesktopWindowManager;

#endregion

namespace AeroWizard
{
  /// <summary>
  ///   Control providing an "Aero Wizard" style interface.
  /// </summary>
  [Designer(typeof(WizardControlDesigner))]
  [ToolboxItem(true), ToolboxBitmap(typeof(WizardControl), "WizardControl.bmp")]
  [Description("Creates an Aero Wizard interface.")]
  [DefaultProperty("Pages"), DefaultEvent("SelectedPageChanged")]
  public partial class WizardControl : UserControl, ISupportInitialize
  {
		#region Fields (11) 

    private string finishBtnText;
    private Point formMoveLastMousePos;
    private bool formMoveTracking;
    private bool initialized;
    private bool initializing;
    private string nextBtnText;
    private readonly Stack<WizardPage> pageHistory;
    private Form parentForm;
    private WizardPage selectedPage;
    private Icon titleImageIcon;
    private bool titleImageIconSet;

		#endregion Fields 

		#region Constructors (1) 

    /// <summary>
    ///   Initializes a new instance of the <see cref = "WizardControl" /> class.
    /// </summary>
    public WizardControl()
    {
      pageHistory = new Stack<WizardPage>();

      Pages = new WizardPageCollection(this);
      Pages.ItemAdded += Pages_AddItem;
      Pages.ItemDeleted += Pages_RemoveItem;
      Pages.Reset += Pages_Reset;

      InitializeComponent();

      // Get localized defaults for button text
      ResetBackButtonText();
      ResetCancelButtonText();
      ResetFinishButtonText();
      ResetNextButtonText();
      ResetTitle();
      ResetTitleIcon();
    }

		#endregion Constructors 

		#region Properties (13) 

    /// <summary>
    ///   Gets or sets the state of the back button.
    /// </summary>
    /// <value>The state of the back button.</value>
    [Browsable(false),
     DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public WizardCommandButtonState BackButtonState
    {
      get { return GetCmdButtonState(backButton); }
      internal set { SetCmdButtonState(backButton, value); }
    }

    /// <summary>
    ///   Gets or sets the back button tool tip text.
    /// </summary>
    /// <value>The back button tool tip text.</value>
    [Category("Wizard"),
     Localizable(true)]
    public string BackButtonText
    {
      get { return backButton.Text; }
      set
      {
        backButton.Text = value;
        base.Invalidate();
      }
    }

    /// <summary>
    ///   Gets the state of the cancel button.
    /// </summary>
    /// <value>The state of the cancel button.</value>
    [Browsable(false),
     DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public WizardCommandButtonState CancelButtonState
    {
      get { return GetCmdButtonState(cancelButton); }
      internal set { SetCmdButtonState(cancelButton, value); }
    }

    /// <summary>
    ///   Gets or sets the cancel button text.
    /// </summary>
    /// <value>The cancel button text.</value>
    [Category("Wizard"),
     Localizable(true)]
    public string CancelButtonText
    {
      get { return cancelButton.Text; }
      set
      {
        cancelButton.Text = value;
        base.Invalidate();
      }
    }

    /// <summary>
    ///   Gets or sets the finish button text.
    /// </summary>
    /// <value>The finish button text.</value>
    [Category("Wizard"),
     Localizable(true)]
    public string FinishButtonText
    {
      get { return finishBtnText; }
      set
      {
        finishBtnText = value;
        if (selectedPage != null && selectedPage.IsFinishPage && !DesignMode)
        {
          nextButton.Text = value;
          nextButton.Invalidate();
        }
      }
    }

    /// <summary>
    ///   Gets or sets the page header text.
    /// </summary>
    /// <value>The page header text.</value>
    [Browsable(false),
     EditorBrowsable(EditorBrowsableState.Never),
     DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public string HeaderText
    {
      get { return headerLabel.Text; }
      set
      {
        headerLabel.Text = value;
        base.Invalidate();
      }
    }

    /// <summary>
    ///   Gets the state of the next button.
    /// </summary>
    /// <value>The state of the next button.</value>
    [Browsable(false),
     DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public WizardCommandButtonState NextButtonState
    {
      get { return GetCmdButtonState(nextButton); }
      internal set { SetCmdButtonState(nextButton, value); }
    }

    /// <summary>
    ///   Gets or sets the next button text.
    /// </summary>
    /// <value>The next button text.</value>
    [Category("Wizard"),
     Localizable(true)]
    public string NextButtonText
    {
      get { return nextBtnText; }
      set
      {
        nextBtnText = value;
        if (!DesignMode && (selectedPage == null || !selectedPage.IsFinishPage))
        {
          nextButton.Text = value;
          nextButton.Invalidate();
        }
      }
    }

    /// <summary>
    ///   Gets the collection of wizard pages in this wizard control.
    /// </summary>
    /// <value>The <see cref = "WizardPageCollection" /> that contains the <see cref = "WizardPage" /> objects in this <see cref = "WizardControl" />.</value>
    [Category("Wizard")]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public WizardPageCollection Pages { get; private set; }

    /// <summary>
    ///   Gets the currently selected wizard page.
    /// </summary>
    /// <value>The selected wizard page. <c>null</c> if no page is active.</value>
    [Browsable(false),
     DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public virtual WizardPage SelectedPage
    {
      get
      {
        if ((selectedPage == null) || (Pages.Count == 0))
          return null;
        return selectedPage;
      }
      internal set
      {
        if (value != null && !Pages.Contains(value))
          throw new ArgumentException("WizardPage is not in the Pages collection for the control.");

        Debug.WriteLine(string.Format("SelectPage: New={0},Prev={1}",
                                      value == null ? "null" : value.Name,
                                      selectedPage == null ? "null" : selectedPage.Name));
        if (value != selectedPage)
        {
          if (selectedPage != null)
            selectedPage.Hide();
          selectedPage = value;
          if (value != null)
          {
            HeaderText = value.Text;
            value.InitializePage(selectedPage);
            selectedPage.Dock = DockStyle.Fill;
            selectedPage.PerformLayout();
            selectedPage.Show();
            selectedPage.BringToFront();
            selectedPage.Focus();
          }
          UpdateButtons();
          OnSelectedPageChanged();
        }
      }
    }

    internal int SelectedPageIndex
    {
      get
      {
        if (selectedPage == null)
          return -1;
        return Pages.IndexOf(selectedPage);
      }
    }

    /// <summary>
    ///   Gets or sets the title for the wizard.
    /// </summary>
    /// <value>The title text.</value>
    [Category("Wizard"),
     Localizable(true)]
    public string Title
    {
      get { return title.Text; }
      set
      {
        title.Text = value;
        base.Invalidate();
      }
    }

    /// <summary>
    ///   Gets or sets the optionally displayed icon next to the wizard title.
    /// </summary>
    /// <value>The title icon.</value>
    [Category("Wizard"), Localizable(true)]
    public Icon TitleIcon
    {
      get { return titleImageIcon; }
      set
      {
        titleImageIcon = value;
        titleImageList.Images.Clear();
        titleImageList.Images.Add(value);
        titleImageIconSet = true;
        base.Invalidate();
      }
    }

		#endregion Properties 

		#region Delegates and Events (3) 

		// Events (3) 

    /// <summary>
    ///   Occurs when the user clicks the Cancel button.
    /// </summary>
    public event EventHandler Cancelled;

    /// <summary>
    ///   Occurs when the user clicks the Next/Finish button and the page is set to <see cref = "WizardPage.IsFinishPage" /> or this is the last page in the <see cref = "Pages" /> collection.
    /// </summary>
    public event EventHandler Finished;

    /// <summary>
    ///   Occurs when the <see cref = "WizardControl.SelectedPage" /> property has changed.
    /// </summary>
    public event EventHandler SelectedPageChanged;

		#endregion Delegates and Events 

		#region Methods (44) 

		// Public Methods (5) 

    /// <summary>
    ///   Signals the object that initialization is starting.
    /// </summary>
    public void BeginInit()
    {
      initializing = true;
    }

    /// <summary>
    ///   Signals the object that initialization is complete.
    /// </summary>
    public void EndInit()
    {
      initializing = false;
    }

    /// <summary>
    ///   Advances to the next page in the sequence.
    /// </summary>
    public void NextPage()
    {
      NextPage(null);
    }

    /// <summary>
    ///   Advances to the specified page.
    /// </summary>
    /// <param name = "nextPage">The wizard page to go to next.</param>
    public virtual void NextPage(WizardPage nextPage)
    {
      if (DesignMode)
      {
        var idx = SelectedPageIndex;
        if (idx < Pages.Count - 1)
          SelectedPage = Pages[idx + 1];
        return;
      }

      if (nextPage == null)
      {
        if (SelectedPage.IsFinishPage || Pages.IndexOf(SelectedPage) == Pages.Count - 1)
        {
          OnFinished();
          return;
        }
      }
      else if (!Pages.Contains(nextPage))
        throw new ArgumentException(
          "When specifying a value for nextPage, it must already be in the Pages collection.", "nextPage");

      if (SelectedPage.CommitPage())
      {
        pageHistory.Push(SelectedPage);
        if (nextPage != null)
          SelectedPage = nextPage;
        else if (SelectedPage.NextPage != null)
          SelectedPage = SelectedPage.NextPage;
        else
          SelectedPage = Pages[Pages.IndexOf(SelectedPage) + 1];
      }
    }

    /// <summary>
    ///   Returns to the previous page.
    /// </summary>
    public virtual void PreviousPage()
    {
      if (DesignMode)
      {
        var idx = SelectedPageIndex;
        if (idx > 0)
          SelectedPage = Pages[idx - 1];
        return;
      }

      if (SelectedPage.RollbackPage())
        SelectedPage = pageHistory.Pop();
    }
		// Protected Methods (8) 

    /// <summary>
    ///   Raises the <see cref = "WizardControl.Cancelled" /> event.
    /// </summary>
    protected virtual void OnCancelled()
    {
      var h = Cancelled;
      if (h != null)
        h(this, EventArgs.Empty);

      if (!DesignMode)
        CloseForm(DialogResult.Cancel);
    }

    /// <summary>
    ///   Raises the <see cref = "E:System.Windows.Forms.Control.ControlAdded" /> event.
    /// </summary>
    /// <param name = "e">A <see cref = "T:System.Windows.Forms.ControlEventArgs" /> that contains the event data.</param>
    protected override void OnControlAdded(ControlEventArgs e)
    {
      base.OnControlAdded(e);
      if (e.Control is WizardPage)
      {
        Controls.Remove(e.Control);
        Pages.Add(e.Control as WizardPage);
      }
    }

    /// <summary>
    ///   Raises the <see cref = "WizardControl.Finished" /> event.
    /// </summary>
    protected virtual void OnFinished()
    {
      var h = Finished;
      if (h != null)
        h(this, EventArgs.Empty);

      if (!DesignMode)
        CloseForm(DialogResult.OK);
    }

    /// <summary>
    ///   Raises the <see cref = "E:System.Windows.Forms.Control.GotFocus" /> event.
    /// </summary>
    /// <param name = "e">An <see cref = "T:System.EventArgs" /> that contains the event data.</param>
    protected override void OnGotFocus(EventArgs e)
    {
      base.OnGotFocus(e);
      if (selectedPage != null)
        selectedPage.Focus();
    }

    /// <summary>
    ///   Raises the <see cref = "E:System.Windows.Forms.Control.HandleCreated" /> event.
    /// </summary>
    /// <param name = "e">An <see cref = "T:System.EventArgs" /> that contains the event data.</param>
    protected override void OnHandleCreated(EventArgs e)
    {
      base.OnHandleCreated(e);
      InitialSetup();
    }

    /// <summary>
    ///   Raises the <see cref = "E:System.Windows.Forms.Control.HandleDestroyed" /> event.
    /// </summary>
    /// <param name = "e">An <see cref = "T:System.EventArgs" /> that contains the event data.</param>
    protected override void OnHandleDestroyed(EventArgs e)
    {
      /* ToDo
      if (isMin6 && !DesignMode)
        DesktopWindowManager.CompositionChanged -= DesktopWindowManager_CompositionChanged;
       */
      base.OnHandleDestroyed(e);
    }

    /// <summary>
    ///   Raises the <see cref = "E:System.Windows.Forms.Control.ParentChanged" /> event.
    /// </summary>
    /// <param name = "e">An <see cref = "T:System.EventArgs" /> that contains the event data.</param>
    protected override void OnParentChanged(EventArgs e)
    {
      base.OnParentChanged(e);

      if (parentForm != null)
      {
        parentForm.HandleCreated -= parentForm_HandleCreated;
        /* ToDo
        if (isMin6 && !DesignMode)
          DesktopWindowManager.CompositionChanged -= DesktopWindowManager_CompositionChanged;
        */
      }
      parentForm = base.Parent as Form;
      Dock = DockStyle.Fill;
      if (parentForm != null)
      {
        parentForm.HandleCreated += parentForm_HandleCreated;
        //Margins margins = new Margins(0) { Top = titleBar.Height };
      }
    }

    /// <summary>
    ///   Raises the <see cref = "WizardControl.SelectedPageChanged" /> event.
    /// </summary>
    protected void OnSelectedPageChanged()
    {
      var temp = SelectedPageChanged;
      if (temp != null)
        temp(this, EventArgs.Empty);
    }
		// Private Methods (30) 

    private void backButton_Click(object sender, EventArgs e)
    {
      PreviousPage();
    }

    private void cancelButton_Click(object sender, EventArgs e)
    {
      OnCancelled();
    }

    private void CloseForm(DialogResult dlgResult)
    {
      var form = base.FindForm();
      if (form != null && form.Modal)
        form.DialogResult = dlgResult;
    }

    private void ConfigureWindowFrame()
    {
      /* ToDo: Check...
      if (isMin6 && DesktopWindowManager.IsCompositionEnabled())
      {
        titleBar.BackColor = Color.Black;
        parentForm.ExtendFrameIntoClientArea(new Padding(0) {Top = titleBar.Height});
      }
      else
        titleBar.BackColor = commandArea.BackColor;
      */
    }

    private void contentArea_Paint(object sender, PaintEventArgs pe)
    {
      if (DesignMode && Pages.Count == 0)
      {
        var noPagesText = Resources.WizardNoPagesNotice;
        var r = GetContentAreaRectangle(false);

        r.Inflate(-2, -2);
        //pe.Graphics.DrawRectangle(SystemPens.GrayText, r);
        ControlPaint.DrawFocusRectangle(pe.Graphics, r);

        var textSize = pe.Graphics.MeasureString(noPagesText, Font);
        r.Inflate((r.Width - (int)textSize.Width) / -2, (r.Height - (int)textSize.Height) / -2);
        pe.Graphics.DrawString(noPagesText, Font, SystemBrushes.GrayText, r);
      }
    }

    private void DesktopWindowManager_CompositionChanged(object sender, EventArgs e)
    {
      ConfigureWindowFrame();
    }

    private WizardCommandButtonState GetCmdButtonState(ButtonBase btn)
    {
      if (!btn.Visible)
        return WizardCommandButtonState.Hidden;
      else
      {
        if (btn.Enabled)
          return WizardCommandButtonState.Enabled;
        else
          return WizardCommandButtonState.Disabled;
      }
    }

    private void InitialSetup()
    {
      if (!initialized)
      {
        if (Pages.Count > 0)
          SelectedPage = Pages[0];
        else
          UpdateButtons();
        initialized = true;
      }
    }

    private void nextButton_Click(object sender, EventArgs e)
    {
      NextPage();
    }

    private void Pages_AddItem(object sender, EventedList<WizardPage>.ListChangedEventArgs<WizardPage> e)
    {
      Pages_AddItemHandler(e.Item, !initializing);
    }

    private void Pages_AddItemHandler(WizardPage item, bool selectAfterAdd)
    {
      Debug.WriteLine(string.Format("AddPage: {0},sel={1}",
                                    item == null ? "null" : item.Text, selectAfterAdd));
      item.Owner = this;
      item.Visible = false;
      if (!contentArea.Contains(item))
        contentArea.Controls.Add(item, 1, 0);
      if (selectAfterAdd)
        SelectedPage = item;
    }

    private void Pages_RemoveItem(object sender, EventedList<WizardPage>.ListChangedEventArgs<WizardPage> e)
    {
      contentArea.Controls.Remove(e.Item);
      if (e.Item == selectedPage && Pages.Count > 0)
        SelectedPage = Pages[e.ItemIndex == Pages.Count ? e.ItemIndex - 1 : e.ItemIndex];
      else
        UpdateButtons();
    }

    private void Pages_Reset(object sender, EventedList<WizardPage>.ListChangedEventArgs<WizardPage> e)
    {
      var curPage = selectedPage;
      SelectedPage = null;
      contentArea.Controls.Clear();
      foreach (var item in Pages)
        Pages_AddItemHandler(item, false);
      if (Pages.Count > 0)
        SelectedPage = Pages.Contains(curPage) ? curPage : Pages[0];
    }

    private void parentForm_HandleCreated(object sender, EventArgs e)
    {
      ConfigureWindowFrame();
      /*
      if (isMin6 && !DesignMode)
        DesktopWindowManager.CompositionChanged += DesktopWindowManager_CompositionChanged;
      */
    }

    private void ResetBackButtonText()
    {
      BackButtonText = Resources.WizardBackButtonToolTip;
    }

    private void ResetCancelButtonText()
    {
      CancelButtonText = Resources.WizardCancelText;
    }

    private void ResetFinishButtonText()
    {
      FinishButtonText = Resources.WizardFinishText;
    }

    private void ResetNextButtonText()
    {
      NextButtonText = Resources.WizardNextText;
    }

    private void ResetTitle()
    {
      Title = Resources.WizardTitle;
    }

    private void ResetTitleIcon()
    {
      TitleIcon = Resources.WizardControlIcon;
      titleImageIconSet = false;
    }

    private void SetCmdButtonState(ButtonBase btn, WizardCommandButtonState value)
    {
      switch (value)
      {
        case WizardCommandButtonState.Disabled:
          btn.Enabled = false;
          btn.Visible = true;
          break;
        case WizardCommandButtonState.Hidden:
          btn.Enabled = false;
          if (btn != backButton)
            btn.Visible = false;
          break;
        case WizardCommandButtonState.Enabled:
        default:
          btn.Enabled = btn.Visible = true;
          break;
      }

      if (btn == cancelButton || btn == nextButton)
      {
        commandArea.Visible = (cancelButton.Visible || nextButton.Visible);
      }

      base.Invalidate();
    }

    private bool ShouldSerializeBackButtonToolTipText()
    {
      return BackButtonText != Resources.WizardBackButtonToolTip;
    }

    private bool ShouldSerializeCancelButtonText()
    {
      return CancelButtonText != Resources.WizardCancelText;
    }

    private bool ShouldSerializeFinishButtonText()
    {
      return FinishButtonText != Resources.WizardFinishText;
    }

    private bool ShouldSerializeNextButtonText()
    {
      return NextButtonText != Resources.WizardNextText;
    }

    private bool ShouldSerializeTitle()
    {
      return Title != Resources.WizardTitle;
    }

    private bool ShouldSerializeTitleIcon()
    {
      return titleImageIconSet;
    }

    private void TitleBar_MouseDown(object sender, MouseEventArgs e)
    {
      if (e.Button == MouseButtons.Left)
      {
        var c = titleBar.GetChildAtPoint(e.Location);
        if (c != backButton)
        {
          formMoveTracking = true;
          formMoveLastMousePos = PointToScreen(e.Location);
        }
      }

      base.OnMouseDown(e);
    }

    private void TitleBar_MouseMove(object sender, MouseEventArgs e)
    {
      if (formMoveTracking)
      {
        var screen = PointToScreen(e.Location);

        var diff = new Point(screen.X - formMoveLastMousePos.X, screen.Y - formMoveLastMousePos.Y);

        var loc = parentForm.Location;
        loc.Offset(diff);
        parentForm.Location = loc;

        formMoveLastMousePos = screen;
      }

      base.OnMouseMove(e);
    }

    private void TitleBar_MouseUp(object sender, MouseEventArgs e)
    {
      if (e.Button == MouseButtons.Left)
        formMoveTracking = false;

      base.OnMouseUp(e);
    }
		// Internal Methods (1) 

    internal Rectangle GetContentAreaRectangle(bool parentRelative)
    {
      var cw = contentArea.GetColumnWidths();
      var ch = contentArea.GetRowHeights();
      var r = new Rectangle(cw[0], 0, cw[1], ch[0]);
      if (parentRelative)
        r.Offset(contentArea.Location);
      return r;
    }

		#endregion Methods 

    /// <summary>
    ///   Updates the buttons according to current sequence and history.
    /// </summary>
    protected internal void UpdateButtons()
    {
      Debug.WriteLine(string.Format("UpdBtn: hstCnt={0},pgIdx={1}:{2},isFin={3}", pageHistory.Count, SelectedPageIndex,
                                    Pages.Count, selectedPage == null ? false : selectedPage.IsFinishPage));
      if (DesignMode)
        BackButtonState = SelectedPageIndex <= 0 ? WizardCommandButtonState.Disabled : WizardCommandButtonState.Enabled;
      else
        BackButtonState = pageHistory.Count == 0 ? WizardCommandButtonState.Disabled : WizardCommandButtonState.Enabled;
      if (selectedPage == null)
      {
        CancelButtonState = DesignMode ? WizardCommandButtonState.Disabled : WizardCommandButtonState.Enabled;
        NextButtonState = WizardCommandButtonState.Hidden;
      }
      else
      {
        if (DesignMode)
        {
          CancelButtonState = WizardCommandButtonState.Disabled;
          NextButtonState = SelectedPageIndex == Pages.Count - 1
                              ? WizardCommandButtonState.Disabled
                              : WizardCommandButtonState.Enabled;
        }
        else
        {
          CancelButtonState = selectedPage.ShowCancel
                                ? (selectedPage.AllowCancel && !DesignMode
                                     ? WizardCommandButtonState.Enabled
                                     : WizardCommandButtonState.Disabled)
                                : WizardCommandButtonState.Hidden;
          NextButtonState = selectedPage.ShowNext
                              ? (selectedPage.AllowNext
                                   ? WizardCommandButtonState.Enabled
                                   : WizardCommandButtonState.Disabled)
                              : WizardCommandButtonState.Hidden;
          if (selectedPage.IsFinishPage || Pages.IndexOf(SelectedPage) == Pages.Count - 1)
            nextButton.Text = FinishButtonText;
          else
            nextButton.Text = NextButtonText;
        }
      }
    }
  }
}